#include "camera.h"
#include "director.h"
#include "../opengl/texture.h"
#include "../opengl/frame_buffer.h"
#include "../../mathematics/size.h"


namespace jlib {


void CameraNode::removeSelf (bool cleanup) {
  _cameraList.detach();
  Node::removeSelf(cleanup);
}



void WindowCamera::onDrawBegan () {
  glEnable(GL_BLEND);
  glEnable(GL_MULTISAMPLE);
  glClearColor(0.0F, 0.0F, 0.0F, 0.0F);
  glClear(GL_COLOR_BUFFER_BIT);
  auto winSize = root()->getWindowSizei();
  glViewport(0, 0, winSize.w, winSize.h);
}

void WindowCamera::onDrawEnded () {
}

Mat4 WindowCamera::projectionMatrix () {
  auto halfWinSize = root()->windowSizef() * 0.5F;
  return Mat4::orthogonal(box3(-halfWinSize.w, -halfWinSize.h, -1.0F, halfWinSize.w, halfWinSize.h, 1.0F));
}

Mat4 WindowCamera::getViewMatrix () {
  auto halfWinSize = root()->windowSizef() * 0.5F;
  return Mat4::lookat(vec3(halfWinSize, 0.0F), vec3(halfWinSize, -1.0F), vec3(0.0F, 1.0F, 0.0F));
}




bool FirstPersonCamera::init (float fovy, float near, float far) noexcept {
  if (fovy <= 0.0F || fovy >= 180)
    return false;
  if (near <= 0.0F || far < near)
    return false;
  this->_fovy = d2r(fovy);
  this->_aspect = 0.0F;
  this->_near = near;
  this->_far = far;
  return true;
}

void FirstPersonCamera::initDraw () {
}

void FirstPersonCamera::endDraw () {
}

mat4 FirstPersonCamera::getProjectionMatrix () {
  auto winSize = this->_root->getWindowSizef();
  float aspect = winSize.w / winSize.h;
  if (this->_aspect != aspect || this->_firstPersonCameraFlags & ProjectionMatrixDirty) {
    this->_projection = mat4::perspective(this->_fovy, this->_aspect = aspect, this->_near, this->_far);
    this->_firstPersonCameraFlags &= ~ProjectionMatrixDirty;
  }
  return this->_projection;
}

mat4 FirstPersonCamera::getViewMatrix () {
  if (this->_baseFlags & SpaceMatrixDirty) {
    auto &m = this->getSpaceMatrix();
    vec4 center = m * vec4(0.0F, 0.0F, 0.0F, 1.0F);
    vec4 target = m * vec4(0.0F, 1.0F, 0.0F, 1.0F);
    vec4 headup = m * vec4(0.0F, 0.0F, 1.0F, 0.0F);
    this->_lookat = mat4::lookat(center, target, headup);
  }
  return this->_lookat;
}

const mat4 &FirstPersonCamera::getLocalMatrix () {
  if (this->_baseFlags & LocalMatrixDirty) {
    this->_localMat = mat4::translate(this->getPosition());
    this->_localMat *= mat4::rotatez(this->getRotationZ());
    this->_localMat *= mat4::rotatex(this->getRotationX());
    this->_localMat *= mat4::rotatey(this->getRotationY());
    this->_baseFlags &= ~LocalMatrixDirty;
  }
  return this->_localMat;
}

void FirstPersonCamera::move (vec3 vector) noexcept {
  auto &m = this->getSpaceMatrix();
  vec3 v = m * vec4(vector, 0.0F);
  this->setPosition(this->getPosition() + v);
}

void FirstPersonCamera::moveUp (float distance) noexcept {
  auto &m = this->getSpaceMatrix();
  vec3 v = m * vec4(0.0F, 0.0F, 1.0F, 0.0F);
  this->setPosition(this->getPosition() + v * distance);
}

void FirstPersonCamera::moveDown (float distance) noexcept {
  auto &m = this->getSpaceMatrix();
  vec3 v = m * vec4(0.0F, 0.0F, -1.0F, 0.0F);
  this->setPosition(this->getPosition() + v * distance);
}

void FirstPersonCamera::moveLeft (float distance) noexcept {
  auto &m = this->getSpaceMatrix();
  vec3 v = m * vec4(-1.0F, 0.0F, 0.0F, 0.0F);
  this->setPosition(this->getPosition() + v * distance);
}

void FirstPersonCamera::moveRight (float distance) noexcept {
  auto &m = this->getSpaceMatrix();
  vec3 v = m * vec4(1.0F, 0.0F, 0.0F, 0.0F);
  this->setPosition(this->getPosition() + v * distance);
}

void FirstPersonCamera::moveForward (float distance) noexcept {
  auto &m = this->getSpaceMatrix();
  vec3 v = m * vec4(0.0F, 1.0F, 0.0F, 0.0F);
  this->setPosition(this->getPosition() + v * distance);
}

void FirstPersonCamera::moveBackward (float distance) noexcept {
  auto &m = this->getSpaceMatrix();
  vec3 v = m * vec4(0.0F, -1.0F, 0.0F, 0.0F);
  this->setPosition(this->getPosition() + v * distance);
}





bool CanvasCamera::init (int w, int h) noexcept {
  if (this->_canvas) {
    if (this->_canvas->width() == w && this->_canvas == h)
      return true;
    this->_canvas = nullptr;
  }
  if (!this->_canvas)
    this->_canvas = Object::create<FrameBuffer>(w, h);
  return this->_canvas && this->_canvas->valid();
}

bool CanvasCamera::init (Texture *tex) noexcept {
  if (!tex || !tex->valid())
    return false;
  if (this->_canvas) {
    if (this->_canvas->colorTex() == tex)
      return true;
    this->_canvas = nullptr;
  }
  if (!this->_canvas)
    this->_canvas = Object::create<FrameBuffer>(tex);
  return this->_canvas && this->_canvas->valid();
}

void CanvasCamera::initDraw () {
  this->_canvas->begin();
}

void CanvasCamera::endDraw () {
  this->_canvas->end();
}


} // namespace jlib
